"""
====================================================================
Copyright (C) 2012-2013 Eric Blond

This file is part of Neptune.

Neptune is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Neptune is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Neptune.  If not, see <http://www.gnu.org/licenses/>.
====================================================================

Created on Feb 18, 2013

@author: Eric
"""

from . import exception

#######

class TypeManager(object):
    
    __slots__ = ('is_compiled', 'content')
    
    def __init__(self, value):
        (self.is_compiled, self.content) = self._normalized(value)
    
    def _normalized(self, value):
        if isinstance(value, type):
            return (True, value)
        elif isinstance(value, str):
            return (False, value)
        elif isinstance(value, tuple):
            result = [self._normalized(x) for x in value]
            return (all(x[0] for x in result), tuple(x[1] for x in result))
        elif isinstance(value, list):
            if len(value) == 1:
                (c, t) = self._normalized(value[0])
                return (c, [t])
        elif isinstance(value, set):
            if len(value) == 1:
                (c, t) = self._normalized(list(value)[0])
                return (c, set([t]))
        elif isinstance(value, dict):
            compiled = True
            result = {}
            for (k, v) in value.items():
                (c, t) = self._normalized(v)
                compiled &= c
                result[k] = t
            return (compiled, result)
        raise exception.DefinitionException('Incorrect type definition "%s"' % repr(value))

    def compile(self):
        if not self.is_compiled:
            self.content = self._compiled(self.content)
            self.is_compiled = True

    def check(self, value):
        self.compile()
        try:
            self._check(value, self.content)
        except exception.TypeException:
            raise exception.TypeException('Value "%s" mismatches with expected type "%s"' % (repr(value), self))

    def _compiled(self, raw):
        if isinstance(raw, str):
            try:
                index = raw.rindex('.')
                current = __import__(raw[:index])
                for part in raw.split('.')[1:]:
                    current = getattr(current, part)
                return current
            except ValueError:
                return globals()[raw]
        elif isinstance(raw, type):
            return raw
        elif isinstance(raw, tuple):
            return tuple(self._compiled(x) for x in raw)
        elif isinstance(raw, list):
            return list(self._compiled(x) for x in raw)
        elif isinstance(raw, set):
            return set(self._compiled(x) for x in raw)
        elif isinstance(raw, dict):
            return dict((k, self._compiled(v)) for (k, v) in raw.items())
        assert False

    def _check(self, value, compiled):
        if isinstance(compiled, type):
            if isinstance(value, compiled):
                return
        elif isinstance(compiled, tuple):
            if isinstance(value, tuple):
                if len(value) == len(compiled):
                    for (e1, e2) in zip(value, compiled):
                        self._check(e1, e2)
                        return
        elif isinstance(compiled, list):
            if isinstance(value, list):
                for x in value:
                    self._check(x, compiled[0])
                return
        elif isinstance(compiled, set):
            if isinstance(value, (set, frozenset)):
                sub = list(compiled)[0]
                for x in value:
                    self._check(x, sub)
                return
        elif isinstance(compiled, dict):
            if isinstance(value, dict):
                good = True
                for (k, v) in compiled.items():
                    if k not in value:
                        good = False
                        break
                    self._check(value[k], v)
                if good:
                    return
        raise exception.TypeException()

    def __str__(self):
        self.compile()
        return self._str(self.content)

    def _str(self, compiled):
        if isinstance(compiled, type):
            if compiled.__module__ != '__builtin__':
                return '%s.%s' % (compiled.__module__, compiled.__name__)
            else:
                return compiled.__name__
        elif isinstance(compiled, tuple):
            if len(compiled) == 1:
                return '(%s,)' % self._str(compiled[0])
            else:
                return '(' + ', '.join(self._str(x) for x in compiled) + ')'
        elif isinstance(compiled, list):
            return '[%s]' % self._str(compiled[0])
        elif isinstance(compiled, set):
            return 'set([%s])' % self._str(list(compiled)[0])
        elif isinstance(compiled, dict):
            return '{' + ', '.join('%s: %s' % (k, self._str(v)) for (k, v) in compiled.items()) + '}'

